package controllers

import (
	"crypto/md5"
	"fmt"
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"io"
	"math/rand"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

type UploadController struct {
	beego.Controller
}

// 上传结果
type UploadResult struct {
	Message  string `json:"message"`
	Data     string `json:"data"`
	FileName string `json:"file_name"`
}

func isExist(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		if os.IsNotExist(err) {
			return false
		}
		fmt.Println(err)
		return false
	}
	return true
}

// @Tags Upload,v2.0
// @Summary 文件上传
// @Description 文件上传，单个，批量，断点续传
// @Param Token header string true "登录成功 Header Token 使用"
// @Param file query string false "文件"
// @Param chunk query int false "分片索引"
// @Success 0 {array} models.Activity "成功"
// @Router /v2//upload [Post]
func (c *UploadController) PostUpload() {
	logs.Info("[==================== PostUpload ===================================]")
	appFilePath := c.GetString("app_file_path") //文件路径前缀
	files, err := c.GetFiles("file")
	if err != nil {
		http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusNoContent)
		return
	}
	var uploadRs []UploadResult
	for i, _ := range files {
		file, err := files[i].Open()
		if err != nil {
			http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
			return
		}
		defer file.Close() //关闭上传的文件，不然的话会出现临时文件不能清除的情况
		ext := path.Ext(files[i].Filename)
		//验证后缀名是否符合要求
		var AllowExtMap map[string]bool = map[string]bool{
			".jpg":  true,
			".jpeg": true,
			".png":  true,
			".mp4":  true,
		}
		if _, ok := AllowExtMap[ext]; !ok {
			c.Ctx.WriteString("后缀名不符合上传要求")
			return
		}

		nfsDir, _ := beego.AppConfig.String("upload_dir")
		//创建目录
		uploadDir := nfsDir + appFilePath
		err = os.MkdirAll(uploadDir, 0777)
		if err != nil {
			http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
			return
		}

		chunk, _ := c.GetInt("chunk") //分片
		if chunk > 0 {
			chunkIndex, _ := c.GetInt("index")        //分片索引
			totalPieces, _ := c.GetInt("totalPieces") //总片数量
			randStr := c.GetString("rand_str")        //随机字符
			logs.Info(fmt.Printf("chunk=%d,index =%d,totalPieces= %d,randStr =%s", chunk, chunkIndex, totalPieces, randStr))
			randFileName := fmt.Sprintf("%x", md5.Sum([]byte(files[i].Filename+randStr)))
			err = os.MkdirAll(uploadDir+"temp/", 0777)
			filePath := uploadDir + "temp/" + randFileName + "_" + strconv.Itoa(chunkIndex) + ext
			logs.Info("分片文件保存路径：" + filePath)

			if uploadRs == nil {
				uploadRs = make([]UploadResult, 1)
			}
			//已经存在则继续，断点续传
			if isExist(filePath) {
				uploadRs[0] = UploadResult{"上传成功", strings.Replace(filePath, nfsDir, "", 1), files[i].Filename}
				continue
			}
			//创建文件
			dst, err := os.Create(filePath)
			if err != nil {
				http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
				return
			}

			//copy the uploaded file to the destination file
			if _, err := io.Copy(dst, file); err != nil {
				http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
				return
			}
			dst.Close()
			// 最后一个分片时合并文件并删除分片
			if chunkIndex == totalPieces {
				//创建新的文件，所有分片文件都写入到此文件中
				newFileName := fmt.Sprintf("%x", md5.Sum([]byte(time.Now().Format("2006_01_02_15_04_05_")+files[i].Filename)))
				dstFile, err := os.Create(uploadDir + newFileName + ext)
				defer dstFile.Close()
				if err != nil {
					http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
					return
				}

				// 缓冲读取
				buf := make([]byte, 1024)
				for i := 1; i <= totalPieces; i++ {
					chunkFileName := uploadDir + "temp/" + randFileName + "_" + strconv.Itoa(i) + ext
					// 参数2：打开模式，所有模式d都在上面,参数3是权限控制
					// w写 r读 x执行   w  2   r  4   x  1
					srcFile, err := os.OpenFile(chunkFileName, os.O_RDWR, 0666)
					if err != nil {
						return
					}
					for {
						// 从源文件读数据
						n, err := srcFile.Read(buf)
						if err == io.EOF {
							fmt.Println("读取完毕")
							break
						}
						if err != nil {
							fmt.Println(err)
							break
						}
						//写出去
						dstFile.Write(buf[:n])
					}
					srcFile.Close()
				}
				//合并完成之后删除分片文件
				err = os.RemoveAll(uploadDir + "temp/")
				fmt.Println("===>>", err)
				uploadRs[0] = UploadResult{"上传成功", strings.Replace(dstFile.Name(), nfsDir, "", 1), files[i].Filename}
			}
		} else {
			//构造文件名称
			rand.Seed(time.Now().UnixNano())
			randNum := fmt.Sprintf("%d", rand.Intn(9999)+1000)
			hashName := md5.Sum([]byte(time.Now().Format("2006_01_02_15_04_05_") + randNum))
			fileName := fmt.Sprintf("%x", hashName) + ext
			//this.Ctx.WriteString(  fileName )

			filePath := uploadDir + fileName
			logs.Info("文件保存路径：" + filePath)
			//创建文件
			dst, err := os.Create(filePath)
			defer dst.Close()
			if err != nil {
				http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
				return
			}

			//copy the uploaded file to the destination file
			if _, err := io.Copy(dst, file); err != nil {
				http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
				return
			}
			if uploadRs == nil {
				uploadRs = make([]UploadResult, len(files))
			}
			uploadRs[i] = UploadResult{"上传成功", strings.Replace(dst.Name(), nfsDir, "", 1), files[i].Filename}
		}
	}
	c.Data["json"] = uploadRs
	c.ServeJSON()
}
